/*
 * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *    http://aws.amazon.com/apache2.0
 *
 * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
 * OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and
 * limitations under the License.
 */

package com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import org.junit.Test;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * Unit test on reflecting domain classes with getter or field annotations. It
 * also tests the scenario when annotated properties are inherited from the
 * superclass.
 */
@SuppressWarnings("unused")
public class PojoReflectionTest {

    private static DynamoDBReflector reflector = new DynamoDBReflector();

    /**
     * Tests reflecting a model class that uses getter annotations.
     */
    @Test
    public void testGetterAnnotations() {
        validateModel(PojoWithGetterAnnotations.class);
    }

    /**
     * Tests reflecting a model class that uses field annotations.
     */
    @Test
    public void testFieldAnnotations() {
        validateModel(PojoWithFieldAnnotations.class);
    }

    /**
     * Tests reflecting a model class that uses both getter and field
     * annotations.
     */
    @Test
    public void testMixedAnnotations() {
        validateModel(PojoWithMixedAnnotations.class);
    }

    /**
     * Validates that the reflected information from the POJO class mathes the
     * model defined in both PojoWithGetterAnnotations and
     * PojoWithFieldAnnotations.
     */
    private void validateModel(Class<?> clazz) {
        // There should be 7 relevant getters (ignoredAttr is excluded)
        assertEquals(7, reflector.getRelevantGetters(clazz).size());

        for (Method getter : reflector.getRelevantGetters(clazz)) {
            // Check that getAttributeName returns the expected attribute name
            assertEquals(
                    expectedAttributeNames.get(getter.getName()),
                    reflector.getAttributeName(getter));

            // @DynamoDBAutoGeneratedKey
            if (getter.getName().equals("getAutogeneratedRangeKey")) {
                assertTrue(reflector.isAssignableKey(getter));
            }

            // @DynamoDBVersionAttribute
            if (getter.getName().equals("getVersionedAttr")) {
                assertTrue(reflector.isVersionAttributeGetter(getter));
            }
        }

        // Key getters
        assertEquals("getHashKey", reflector.getPrimaryHashKeyGetter(clazz).getName());
        assertEquals("hashKey", reflector.getPrimaryHashKeyName(clazz));
        assertEquals("getAutogeneratedRangeKey", reflector.getPrimaryRangeKeyGetter(clazz)
                .getName());
        assertEquals("autogeneratedRangeKey", reflector.getPrimaryRangeKeyName(clazz));
    }

    /**
     * A POJO model that uses getter annotations.
     */
    @DynamoDBTable(tableName = "table")
    private static class PojoWithGetterAnnotations {
        private String hashKey;
        private String autogeneratedRangeKey;
        private String indexHashKey;
        private String indexRangeKey;
        private String attrWithAttrAnnotation;
        private String versionedAttr;
        private String customMarshallingAttr;
        private String ignoredAttr;

        @DynamoDBHashKey
        public String getHashKey() {
            return hashKey;
        }

        public void setHashKey(String hashKey) {
            this.hashKey = hashKey;
        }

        @DynamoDBRangeKey
        @DynamoDBAutoGeneratedKey
        public String getAutogeneratedRangeKey() {
            return autogeneratedRangeKey;
        }

        public void setAutogeneratedRangeKey(String autogeneratedRangeKey) {
            this.autogeneratedRangeKey = autogeneratedRangeKey;
        }

        @DynamoDBIndexHashKey(globalSecondaryIndexName = "index")
        public String getIndexHashKey() {
            return indexHashKey;
        }

        public void setIndexHashKey(String indexHashKey) {
            this.indexHashKey = indexHashKey;
        }

        @DynamoDBIndexRangeKey(globalSecondaryIndexName = "index")
        public String getIndexRangeKey() {
            return indexRangeKey;
        }

        public void setIndexRangeKey(String indexRangeKey) {
            this.indexRangeKey = indexRangeKey;
        }

        @DynamoDBAttribute(attributeName = "real-attribute-name")
        public String getAttrWithAttrAnnotation() {
            return attrWithAttrAnnotation;
        }

        public void setAttrWithAttrAnnotation(String attrWithAttrAnnotation) {
            this.attrWithAttrAnnotation = attrWithAttrAnnotation;
        }

        @DynamoDBVersionAttribute
        public String getVersionedAttr() {
            return versionedAttr;
        }

        public void setVersionedAttr(String versionedAttr) {
            this.versionedAttr = versionedAttr;
        }

        @DynamoDBMarshalling(marshallerClass = RandomUUIDMarshaller.class)
        public String getCustomMarshallingAttr() {
            return customMarshallingAttr;
        }

        public void setCustomMarshallingAttr(String customMarshallingAttr) {
            this.customMarshallingAttr = customMarshallingAttr;
        }

        @DynamoDBIgnore
        public String getIgnoredAttr() {
            return ignoredAttr;
        }

        public void setIgnoredAttr(String ignoredAttr) {
            this.ignoredAttr = ignoredAttr;
        }
    }

    /**
     * The same model as defined in PojoWithGetterAnnotations, but uses field
     * annotations instead.
     */
    @DynamoDBTable(tableName = "table")
    private static class PojoWithFieldAnnotations {
        @DynamoDBHashKey
        private String hashKey;

        @DynamoDBRangeKey
        @DynamoDBAutoGeneratedKey
        private String autogeneratedRangeKey;

        @DynamoDBIndexHashKey(globalSecondaryIndexName = "index")
        private String indexHashKey;

        @DynamoDBIndexRangeKey(globalSecondaryIndexName = "index")
        private String indexRangeKey;

        @DynamoDBAttribute(attributeName = "real-attribute-name")
        private String attrWithAttrAnnotation;

        @DynamoDBVersionAttribute
        private String versionedAttr;

        @DynamoDBMarshalling(marshallerClass = RandomUUIDMarshaller.class)
        private String customMarshallingAttr;

        @DynamoDBIgnore
        private String ignoredAttr;

        public String getHashKey() {
            return hashKey;
        }

        public void setHashKey(String hashKey) {
            this.hashKey = hashKey;
        }

        public String getAutogeneratedRangeKey() {
            return autogeneratedRangeKey;
        }

        public void setAutogeneratedRangeKey(String autogeneratedRangeKey) {
            this.autogeneratedRangeKey = autogeneratedRangeKey;
        }

        public String getIndexHashKey() {
            return indexHashKey;
        }

        public void setIndexHashKey(String indexHashKey) {
            this.indexHashKey = indexHashKey;
        }

        public String getIndexRangeKey() {
            return indexRangeKey;
        }

        public void setIndexRangeKey(String indexRangeKey) {
            this.indexRangeKey = indexRangeKey;
        }

        public String getAttrWithAttrAnnotation() {
            return attrWithAttrAnnotation;
        }

        public void setAttrWithAttrAnnotation(String attrWithAttrAnnotation) {
            this.attrWithAttrAnnotation = attrWithAttrAnnotation;
        }

        public String getVersionedAttr() {
            return versionedAttr;
        }

        public void setVersionedAttr(String versionedAttr) {
            this.versionedAttr = versionedAttr;
        }

        public String getCustomMarshallingAttr() {
            return customMarshallingAttr;
        }

        public void setCustomMarshallingAttr(String customMarshallingAttr) {
            this.customMarshallingAttr = customMarshallingAttr;
        }

        public String getIgnoredAttr() {
            return ignoredAttr;
        }

        public void setIgnoredAttr(String ignoredAttr) {
            this.ignoredAttr = ignoredAttr;
        }
    }

    /**
     * The same model as defined in PojoWithGetterAnnotations, but uses both
     * getter and field annotations.
     */
    @DynamoDBTable(tableName = "table")
    private static class PojoWithMixedAnnotations {
        @DynamoDBHashKey
        private String hashKey;

        private String autogeneratedRangeKey;

        @DynamoDBIndexHashKey(globalSecondaryIndexName = "index")
        private String indexHashKey;

        private String indexRangeKey;

        @DynamoDBAttribute(attributeName = "real-attribute-name")
        private String attrWithAttrAnnotation;

        private String versionedAttr;

        @DynamoDBMarshalling(marshallerClass = RandomUUIDMarshaller.class)
        private String customMarshallingAttr;

        private String ignoredAttr;

        public String getHashKey() {
            return hashKey;
        }

        public void setHashKey(String hashKey) {
            this.hashKey = hashKey;
        }

        @DynamoDBRangeKey
        @DynamoDBAutoGeneratedKey
        public String getAutogeneratedRangeKey() {
            return autogeneratedRangeKey;
        }

        public void setAutogeneratedRangeKey(String autogeneratedRangeKey) {
            this.autogeneratedRangeKey = autogeneratedRangeKey;
        }

        public String getIndexHashKey() {
            return indexHashKey;
        }

        public void setIndexHashKey(String indexHashKey) {
            this.indexHashKey = indexHashKey;
        }

        @DynamoDBIndexRangeKey(globalSecondaryIndexName = "index")
        public String getIndexRangeKey() {
            return indexRangeKey;
        }

        public void setIndexRangeKey(String indexRangeKey) {
            this.indexRangeKey = indexRangeKey;
        }

        public String getAttrWithAttrAnnotation() {
            return attrWithAttrAnnotation;
        }

        public void setAttrWithAttrAnnotation(String attrWithAttrAnnotation) {
            this.attrWithAttrAnnotation = attrWithAttrAnnotation;
        }

        @DynamoDBVersionAttribute
        public String getVersionedAttr() {
            return versionedAttr;
        }

        public void setVersionedAttr(String versionedAttr) {
            this.versionedAttr = versionedAttr;
        }

        public String getCustomMarshallingAttr() {
            return customMarshallingAttr;
        }

        public void setCustomMarshallingAttr(String customMarshallingAttr) {
            this.customMarshallingAttr = customMarshallingAttr;
        }

        @DynamoDBIgnore
        public String getIgnoredAttr() {
            return ignoredAttr;
        }

        public void setIgnoredAttr(String ignoredAttr) {
            this.ignoredAttr = ignoredAttr;
        }
    }

    @SuppressWarnings("serial")
    private static final Map<String, String> expectedAttributeNames = new HashMap<String, String>() {
        {
            put("getHashKey", "hashKey");
            put("getAutogeneratedRangeKey", "autogeneratedRangeKey");
            put("getIndexHashKey", "indexHashKey");
            put("getIndexRangeKey", "indexRangeKey");
            put("getAttrWithAttrAnnotation", "real-attribute-name"); // w/
                                                                     // attribute
                                                                     // name
                                                                     // override
            put("getVersionedAttr", "versionedAttr");
            put("getCustomMarshallingAttr", "customMarshallingAttr");
        }
    };

    @Test
    public void testInheritedProperties() {
        // Base class
        assertEquals(3, reflector.getRelevantGetters(BaseTablePojo.class).size());
        assertEquals("getParentHashKeyWithFieldAnnotation",
                reflector.getPrimaryHashKeyGetter(BaseTablePojo.class).getName());
        assertEquals("parentHashKeyWithFieldAnnotation",
                reflector.getPrimaryHashKeyName(BaseTablePojo.class));
        assertEquals("getParentRangeKeyWithGetterAnnotation",
                reflector.getPrimaryRangeKeyGetter(BaseTablePojo.class).getName());
        assertEquals("parentRangeKeyWithGetterAnnotation",
                reflector.getPrimaryRangeKeyName(BaseTablePojo.class));

        // Subclass pojo inherits the key getters, and defines an attribute that
        // is ignored in the superclass
        assertEquals(4, reflector.getRelevantGetters(TablePojoSubclass.class).size());
        assertEquals(reflector.getPrimaryHashKeyGetter(BaseTablePojo.class),
                reflector.getPrimaryHashKeyGetter(TablePojoSubclass.class));
        assertEquals("parentHashKeyWithFieldAnnotation",
                reflector.getPrimaryHashKeyName(TablePojoSubclass.class));
        assertEquals(reflector.getPrimaryRangeKeyGetter(BaseTablePojo.class),
                reflector.getPrimaryRangeKeyGetter(TablePojoSubclass.class));
        assertEquals("parentRangeKeyWithGetterAnnotation",
                reflector.getPrimaryRangeKeyName(TablePojoSubclass.class));
    }

    @DynamoDBTable(tableName = "table")
    private static class BaseTablePojo {
        @DynamoDBHashKey
        private String parentHashKeyWithFieldAnnotation;
        private String parentRangeKeyWithGetterAnnotation;
        private String parentAttrWithNoAnnotation;
        @DynamoDBIgnore
        private String parentIgnoredAttr;

        public String getParentHashKeyWithFieldAnnotation() {
            return parentHashKeyWithFieldAnnotation;
        }

        public void setParentHashKeyWithFieldAnnotation(
                String parentHashKeyWithFieldAnnotation) {
            this.parentHashKeyWithFieldAnnotation = parentHashKeyWithFieldAnnotation;
        }

        @DynamoDBRangeKey
        public String getParentRangeKeyWithGetterAnnotation() {
            return parentRangeKeyWithGetterAnnotation;
        }

        public void setParentRangeKeyWithGetterAnnotation(
                String parentRangeKeyWithGetterAnnotation) {
            this.parentRangeKeyWithGetterAnnotation = parentRangeKeyWithGetterAnnotation;
        }

        public String getParentAttrWithNoAnnotation() {
            return parentAttrWithNoAnnotation;
        }

        public void setParentAttrWithNoAnnotation(String parentAttrWithNoAnnotation) {
            this.parentAttrWithNoAnnotation = parentAttrWithNoAnnotation;
        }

        public String getParentIgnoredAttr() {
            return parentIgnoredAttr;
        }

        public void setParentIgnoredAttr(String parentIgnoredAttr) {
            this.parentIgnoredAttr = parentIgnoredAttr;
        }
    }

    /**
     * Subclass of BaseTablePojo that inherits all the key attribtues, and
     * declared the parentIgnoredAttr which is ignored in the superclass.
     */
    @DynamoDBTable(tableName = "table")
    private static class TablePojoSubclass extends BaseTablePojo {
        // Not ignored by the subclass
        private String parentIgnoredAttr;

        @Override
        public String getParentIgnoredAttr() {
            return parentIgnoredAttr;
        }

        @Override
        public void setParentIgnoredAttr(String parentIgnoredAttr) {
            this.parentIgnoredAttr = parentIgnoredAttr;
        }
    }
}
